package hu.elte.inf.holiuml.model;

import hu.elte.inf.holiuml.coordgeom.CGPoint;
import hu.elte.inf.holiuml.coordgeom.CGVector;
import hu.elte.inf.holiuml.coordgeom.Line;
import hu.elte.inf.holiuml.coordgeom.LineSegment;
import hu.elte.inf.holiuml.coordgeom.TextUtils;

import java.awt.Graphics;
import java.awt.Point;
import java.io.Serializable;
import java.util.Vector;

/**
 * @author Csaba Hoch
 */
abstract public class Edge implements Drawable, Serializable {

	public Edge() {

	}

	/**
	 * Draws an edge between the two given nodes. The color of the edge will be
	 * the color set in g.
	 */
	static public void drawEdge(Graphics g, Node nodeBegin, Node nodeEnd) {
		if (nodeBegin != null && nodeEnd != null) {
			int x1 = nodeBegin.getX() + nodeBegin.getWidth() / 2;
			int y1 = nodeBegin.getY() + nodeBegin.getHeight() / 2;
			int x2 = nodeEnd.getX() + nodeEnd.getWidth() / 2;
			int y2 = nodeEnd.getY() + nodeEnd.getHeight() / 2;
			g.drawLine(x1, y1, x2, y2);
		}
	}
	
	public void draw(Graphics g) {
		LineSegment ls = getLineSegment();
		if (ls != null) {
			ls.draw(g);
			g.setColor(java.awt.Color.BLACK);
			Vector<String> v = getTextVector();
			if (v != null && getNodeBegin() != null && getNodeEnd() != null) {
				for (int i = 0; i < 6; i++) {
					String text = v.get(i);
					if (text != null) {
						drawText(g, text, i);
					}
				}
			}
		}
	}

	private void drawText(Graphics g, String text, int i) {
		TextUtils tu = new TextUtils(g);
		boolean master = (i % 2 == 0);
		CGPoint centerPosition;
		if ((i / 2) == 0) {
			centerPosition = getNodeTextPosition(text, master, true, g);
		} else if ((i / 2) == 1) {
			centerPosition = getCenterTextPosition(text, master, g);
		} else {
			centerPosition = getNodeTextPosition(text, master, false, g);
		}
		if (centerPosition != null) {
			tu.drawStringCenter(text, centerPosition);
		}
	}

	/**
	 * Calculates where the given text should be exactly drawn.
	 * 
	 * @param text
	 *            The text that should be drawn.
	 * @param master
	 *            True if the text should be drawn into the master side.
	 * @param g
	 *            The graphic object that will be used for the calculation.
	 * @return The central point of the text.
	 */
    //
    //                     | l2    
    // +-------+           |           +-------+
    // |       |           |           |       |
    // |       | ls1, l1   |p          |       |
    // | node1 X-----------X-----------X node2 |    o (x,y) vector
    // |       |           |           |       |    |
    // |       | ----------X---------- |       |    V
    // +-------+   l3      |           +-------+
    //                     |       
    //
    CGPoint getCenterTextPosition(String text, boolean master, Graphics g) {
        LineSegment ls1 = getLineSegment();
        Line l1 = ls1.getLine();
        CGPoint p = ls1.getCenterPoint();
        CGVector d = l1.getDirVector();
		CGVector normalVector = (master ? d.getRotatedPositive() : d
				.getRotatedNegative());
		Line l2 = new Line(p, normalVector);
		TextUtils tu = new TextUtils(g);
		int w = tu.stringWidth(text);
		int h = tu.lineHeight();
		int x = (int) Math.signum(normalVector.getX()) * (w / 2 + EDGE_MARGIN);
		int y = (int) Math.signum(normalVector.getY()) * (h / 2 + EDGE_MARGIN);
		Line l3 = l1.addVector(new CGVector(x, y));
		return l2.intersection(l3);
	}

	/**
	 * Calculates where the given text should be exactly drawn.
	 * 
	 * @param text
	 *            The text that should be drawn.
	 * @param master
	 *            True if the text should be drawn into the master side.
	 * @param beginning
	 *            True if the text should be drawn to the beginning node, false
	 *            if it should be drawn to the ending node.
	 * @param g
	 *            The graphic object that will be used for the calculation.
	 * @return The central point of the text.
	 */
    //         | l2|
    //      l1 |   |
    //         |   |                  
    //         |   |                  
    //         |   |                  
    // +-------+   |             l5   +-------+
    // |       | --|----------------  |       |  ^ 
    // |       |   |                  |       |  | v2
    // | node1 X-------------------->-X node2 |  o
    // |       |   |             l4   |       |
    // |       |   |                  |       |
    // +-------+   |                  +-------+
    //
    //         o->                     
    //          v1
	private CGPoint getNodeTextPosition(String text, boolean master,
			boolean beginning, Graphics g) {
		Node node = beginning ? getNodeBegin() : getNodeEnd();
		Node otherNode = !beginning ? getNodeBegin() : getNodeEnd();
		Point c = otherNode.getPoint(Node.HorizontalDirection.CENTER,
				Node.VerticalDirection.CENTER);
		Node.Direction dir = node.getSpecDirection(c);
		Line l1 = node.getSideLine(dir);
		CGVector v1 = neighbourVectorToAdd(dir);
		TextUtils tu = new TextUtils(g);
		int h = tu.lineHeight();
		int w = tu.stringWidth(text);
		Line l2 = l1.addVector(new CGVector(v1.getX() * (w / 2 + MARGIN), 
				v1.getY() * (h / 2 + MARGIN)));
		Line l3 = getLineSegment().getLine();
		Line l4 = (beginning ? l3 : l3.getFlipped());
		CGVector v2 = edgeParallelVectorToAdd(dir, l1.getDirVector(), l4
				.getDirVector(), master);
		Line l5 = l4.addVector(new CGVector(v2.getX() * (w / 2 + EDGE_MARGIN), 
				v2.getY() * (h / 2 + EDGE_MARGIN)));
		return l2.intersection(l5);
	}

    private CGVector edgeParallelVectorToAdd(Node.Direction d, CGVector v1,
			CGVector v2, boolean master) {
		if (!master) {
			return edgeParallelVectorToAdd(d, v1, v2, true).getFlipped();
		} else {
			boolean leftIsMaster = (v1.scalarProduct(v2) < 0);
			if (d == Node.Direction.EAST && leftIsMaster) {
				return new CGVector(1, -1);
			} else if (d == Node.Direction.EAST && !leftIsMaster) {
				return new CGVector(1, 1);
			} else if (d == Node.Direction.WEST && leftIsMaster) {
				return new CGVector(-1, 1);
			} else if (d == Node.Direction.WEST && !leftIsMaster) {
				return new CGVector(-1, -1);
			} else if (d == Node.Direction.SOUTH && leftIsMaster) {
				return new CGVector(1, 1);
			} else if (d == Node.Direction.SOUTH && !leftIsMaster) {
				return new CGVector(-1, 1);
			} else if (d == Node.Direction.NORTH && leftIsMaster) {
				return new CGVector(-1, -1);
			} else {
				return new CGVector(1, -1);
			}
		}
	}

	private CGVector neighbourVectorToAdd(Node.Direction d) {
		if (d == Node.Direction.EAST) {
			return new CGVector(1, 0);
		} else if (d == Node.Direction.SOUTH) {
			return new CGVector(0, 1);
		} else if (d == Node.Direction.NORTH) {
			return new CGVector(0, -1);
		} else {
			return new CGVector(-1, 0);
		}
	}

	/**
	 * Returns the line segment of the edge. 
	 */
	public LineSegment getLineSegment() {
		Node nodeBegin = getNodeBegin();
		Node nodeEnd = getNodeEnd();
		if (nodeBegin != null && nodeEnd != null) {
			Point c1 = nodeBegin.getPoint(Node.HorizontalDirection.CENTER,
					Node.VerticalDirection.CENTER);
			Point c2 = nodeEnd.getPoint(Node.HorizontalDirection.CENTER,
					Node.VerticalDirection.CENTER);
			CGPoint p1 = new CGPoint(nodeBegin.getSpecPoint(c2));
			CGPoint p2 = new CGPoint(nodeEnd.getSpecPoint(c1));
			return new LineSegment(p1, p2);
		} else {
			return null;
		}
	}

	abstract public Node getNodeBegin();

	abstract public void setNodeBegin(Node nodeBegin);

	abstract public Node getNodeEnd();

	abstract public void setNodeEnd(Node nodeEnd);

	/**
	 * Returns whether the given point is near the node on the canvas.
	 * 
	 * @param p
	 *            The given point.
	 */
	public boolean inside(Point p) {
		LineSegment ls = getLineSegment();
		if (ls != null && ls.near(new CGPoint(p))) {
			return true;
		} else {
			return false;
		}
	}
	
    /**
	 * The elements of this vector contain the text labels that should be
	 * printed on the edge.
	 */
    //
    // v[0]: master near beginning node
    // v[1]: slave near beginning node
    // v[2]: master at the center
    // v[3]: slave at the center
    // v[4]: master near ending node
    // v[5]: slave near ending node
    //
    // +-------+      
    // |       |             
    // |       |v[0]                  +-------+  
    // | node1 X------  v[2]          |       |  
    // |       |v[1]  -------     v[5]|       |  
    // |       |        v[3] ---------X node2 |  
    // +-------+                  v[4]|       |  
    //                                |       |  
    //                                +-------+  
	abstract public Vector<String> getTextVector();
}
